{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploy a Python model as a realtime web service in Machine Learning Server "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "                               ***Applies to: Machine Learning Server 9.2***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook, we use the attitude dataset to build a linear model to predict the `rating` based on other columns. \n",
    "\n",
    "## 1. Read in the attitude dataset\n",
    "\n",
    "From your local machine, let's begin by reading in the data we will use to build our linear model. We will use the dataset `attitude`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>rating</th>\n",
       "      <th>complaints</th>\n",
       "      <th>privileges</th>\n",
       "      <th>learning</th>\n",
       "      <th>raises</th>\n",
       "      <th>critical</th>\n",
       "      <th>advance</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>43.0</td>\n",
       "      <td>51.0</td>\n",
       "      <td>30.0</td>\n",
       "      <td>39.0</td>\n",
       "      <td>61.0</td>\n",
       "      <td>92.0</td>\n",
       "      <td>45.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>63.0</td>\n",
       "      <td>64.0</td>\n",
       "      <td>51.0</td>\n",
       "      <td>54.0</td>\n",
       "      <td>63.0</td>\n",
       "      <td>73.0</td>\n",
       "      <td>47.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>71.0</td>\n",
       "      <td>70.0</td>\n",
       "      <td>68.0</td>\n",
       "      <td>69.0</td>\n",
       "      <td>76.0</td>\n",
       "      <td>86.0</td>\n",
       "      <td>48.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>61.0</td>\n",
       "      <td>63.0</td>\n",
       "      <td>45.0</td>\n",
       "      <td>47.0</td>\n",
       "      <td>54.0</td>\n",
       "      <td>84.0</td>\n",
       "      <td>35.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>81.0</td>\n",
       "      <td>78.0</td>\n",
       "      <td>56.0</td>\n",
       "      <td>66.0</td>\n",
       "      <td>71.0</td>\n",
       "      <td>83.0</td>\n",
       "      <td>47.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   rating  complaints  privileges  learning  raises  critical  advance\n",
       "0    43.0        51.0        30.0      39.0    61.0      92.0     45.0\n",
       "1    63.0        64.0        51.0      54.0    63.0      73.0     47.0\n",
       "2    71.0        70.0        68.0      69.0    76.0      86.0     48.0\n",
       "3    61.0        63.0        45.0      47.0    54.0      84.0     35.0\n",
       "4    81.0        78.0        56.0      66.0    71.0      83.0     47.0"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# -- Import the dataset from the microsoftml package\n",
    "from microsoftml.datasets.datasets import DataSetAttitude\n",
    "attitude = DataSetAttitude()\n",
    "\n",
    "# -- Represent the dataset as a dataframe.\n",
    "attitude = attitude.as_df().drop('Unnamed: 0', axis = 1).astype('double')\n",
    "\n",
    "# -- print top rows of data to inspect the data\n",
    "attitude.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Authenticate and initiate the  `DeployClient`\n",
    "\n",
    "There are several ways to authentication against Machine Learning Server from your local machine. The method you choose should match the authentication defined by your administrator. Please contact your administrator for authentication credentials. \n",
    "\n",
    "For simplicity, this example uses the local 'admin' account for authentication.  \n",
    "\n",
    "1. Begin by importing the DeployClient and MLServer classes from the [azureml-model-management-sdk package](https://docs.microsoft.com/en-us/r-server/python-reference/azureml-model-management-sdk/azureml-model-management-sdk) so you can connect to Machine Learning Server (`use=MLServer`).\n",
    "\n",
    "1. Then, **fill in your own connection details** for the host and context into the corresponding fields. Learn more in the article \"[Connecting to Machine Learning Server in Python](https://docs.microsoft.com/en-us/r-server/operationalize/python/how-to-authenticate-in-python ).\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# -- Import the DeployClient and MLServer classes from the azureml-model-management-sdk package.\n",
    "from azureml.deploy import DeployClient\n",
    "from azureml.deploy.server import MLServer\n",
    "\n",
    "# -- Define the location of the ML Server --\n",
    "# -- for local onebox for Machine Learning Server: http://localhost:12800\n",
    "# -- Replace with connection details to your instance of ML Server. \n",
    "HOST = 'http://localhost:12800'\n",
    "context = ('admin', 'YOUR_ADMIN_PASSWORD')\n",
    "client = DeployClient(HOST, use=MLServer, auth=context)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You are now authenticated. \n",
    "\n",
    "The **DeployClient** can interact with the web service management APIs to deploy, list, consume and so on. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Create and run a linear model locally\n",
    "\n",
    "Now that you have built the authentication logic into your application, you can interact with the core APIs using functions in azureml-model-management-sdk to create a model and publish it as a web service.\n",
    "\n",
    "Create a GLM model called `model` using the `attitude` dataset we imported before. Using data, this model estimates the attitude rating.\n",
    "\n",
    "We use the [rx_lin_mod](https://docs.microsoft.com/en-us/r-server/python-reference/revoscalepy/rx-lin-mod) function from the [revoscalepy package](https://docs.microsoft.com/en-us/r-server/python-reference/revoscalepy/revoscalepy-package) to build the model.\n",
    "\n",
    "Then, you can make a prediction locally using this model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rows Read: 30, Total Rows Processed: 30, Total Chunk Time: 0.002 seconds \n",
      "Computation time: 0.007 seconds.\n",
      "Rows Read: 1, Total Rows Processed: 1, Total Chunk Time: 0.001 seconds \n",
      "   rating_Pred\n",
      "0    51.110295\n"
     ]
    }
   ],
   "source": [
    "# -- import the needed classes and functions\n",
    "from revoscalepy import rx_lin_mod, rx_predict\n",
    "\n",
    "# -- using rx_lin_mod from revoscalepy package\n",
    "# -- create glm model with `attitude` dataset\n",
    "df = attitude\n",
    "form = \"rating ~ complaints + privileges + learning + raises + critical + advance\"\n",
    "model = rx_lin_mod(form, df, method = 'regression')\n",
    "\n",
    "# -- provide some sample inputs to test the model\n",
    "myData = df.head(1)\n",
    "\n",
    "# -- predict locally\n",
    "print(rx_predict(model, myData))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Examine the results of the locally executed code. You can compare these results to the results of the web service in this next step."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Publish the model as a realtime web service\n",
    "\n",
    "Now let's:\n",
    "+ Serialize the model\n",
    "+ Publish the linear model as a Python web service\n",
    "\n",
    "To publish any model as a realtime service, you must serialize the model object using the `rx_serialize_model` function from revoscalepy. Other serialization method are unsupported."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Import the needed classes and functions\n",
    "from revoscalepy import rx_serialize_model\n",
    "\n",
    "# Serialize the model with rx_serialize_model\n",
    "s_model = rx_serialize_model(model, realtime_scoring_only=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To publish the realtime service, we first initiate a `realtimeDefinition` object. Then, you can annotate the object with other parameters.\n",
    "\n",
    "Now, publish it as a realtime service to Machine Learning Server. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "service = client.realtime_service(\"LinModService\") \\\n",
    "        .version('1.0') \\\n",
    "        .serialized_model(s_model) \\\n",
    "        .description(\"This is a realtime model.\") \\\n",
    "        .deploy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**To learn more about deploying a web service into Machine Learning Server, [see here](https://docs.microsoft.com/en-us/machine-learning-server/operationalize/python/how-to-deploy-manage-web-services).**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Explore and consume the published web service\n",
    "\n",
    "Now let's:\n",
    "+ Use the help function to explore the published service. You can call the `help` function on any `azureml-model-management-sdk` functions, even those we dynamically generated ones to learn more about them.\n",
    "\n",
    "+ Print the capabilities that define the service holdings: service name, version, descriptions, inputs, outputs, and the name of the function to be consumed.\n",
    "\n",
    "+ Predict an outcome.\n",
    "\n",
    "+ Download the Swagger-based JSON file.  This file is auto-generated at deploy time.  You can share it with any authenticated users so they can test and consume the service.   **You can share the resulting file with application developers or others testing your service.** Learn more about [exploring and consuming in this notebook](https://github.com/Microsoft/ML-Server-Python-Samples/blob/master/web-services/deploy-consume/Explore_Consume_Python_Web_Services.ipynb)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on LinmodserviceService in module azureml.deploy.server.service object:\n",
      "\n",
      "class LinmodserviceService(Service)\n",
      " |  Service object from metadata.\n",
      " |  \n",
      " |  Method resolution order:\n",
      " |      LinmodserviceService\n",
      " |      Service\n",
      " |      builtins.object\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, service, http_client)\n",
      " |      Constructor\n",
      " |      \n",
      " |      :param service:\n",
      " |      :param http_client:\n",
      " |  \n",
      " |  __str__(self)\n",
      " |      Return str(self).\n",
      " |  \n",
      " |  batch(self, records, parallel_count=10)\n",
      " |      Register a set of input records for batch execution on this service.\n",
      " |      \n",
      " |      :param records: The `data.frame` or `list` of\n",
      " |             input records to execute.\n",
      " |      :param parallel_count: Number of threads used to process entries in\n",
      " |             the batch. Default value is 10. Please make sure not to use too\n",
      " |             high of a number because it might negatively impact performance.\n",
      " |      :return: The `Batch` object to control service batching\n",
      " |              lifecycle.\n",
      " |  \n",
      " |  capabilities(self)\n",
      " |      Gets the service holding capabilities.\n",
      " |      \n",
      " |      :return: A dict of key/values describing the service.\n",
      " |  \n",
      " |  consume(self, inputData)\n",
      " |      Consume the LinModService service.\n",
      " |      \n",
      " |      This is a realtime model.\n",
      " |      \n",
      " |      :param pandas.DataFrame inputData: The required service input.    \n",
      " |      :returns ServiceResponse: The `<ServiceResponse>` object contains the set of\n",
      " |          expected output values and artifacts. The possible outputs include:\n",
      " |                  \n",
      " |          Output: pandas.DataFrame outputData        \n",
      " |      \n",
      " |      :Raises:\n",
      " |          HttpException: If server errors occur while executing the service.\n",
      " |          ValueError: If argument input types do not match the expected service\n",
      " |              input types.\n",
      " |  \n",
      " |  get_batch(self, execution_id)\n",
      " |      Retrieve the `Batch` based on an `execution id`\n",
      " |      \n",
      " |      :param execution_id: The id of the batch execution.\n",
      " |      :return: The `Batch`.\n",
      " |  \n",
      " |  list_batch_executions(self)\n",
      " |      Gets all batch executions currently queued for this service.\n",
      " |      \n",
      " |      :return: A list of `execution ids`.\n",
      " |  \n",
      " |  swagger(self)\n",
      " |      Retrieves the `swagger.json` for this service (see http://swagger.io/).\n",
      " |      :return: The swagger document for this service.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Static methods defined here:\n",
      " |  \n",
      " |  __new__(cls, *args, **kwargs)\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "print(help(service))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Explore all available functions on the service object by calling `capabilities`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'api': '/api/LinModService/1.0',\n",
       " 'artifacts': [],\n",
       " 'creation_time': '2017-10-18T21:55:46.085642',\n",
       " 'description': 'This is a realtime model.',\n",
       " 'inputs': [{'name': 'inputData', 'type': 'data.frame'}],\n",
       " 'inputs_encoded': [{'name': 'inputData', 'type': 'pandas.DataFrame'}],\n",
       " 'name': 'LinModService',\n",
       " 'operation_id': 'consume',\n",
       " 'outputs': [{'name': 'outputData', 'type': 'data.frame'}],\n",
       " 'outputs_encoded': [{'name': 'outputData', 'type': 'pandas.DataFrame'}],\n",
       " 'public-functions': {'batch': 'batch(records, parallel_count=10)',\n",
       "  'capabilities': 'capabilities()',\n",
       "  'consume': 'consume(self,inputData)',\n",
       "  'get_batch': 'get_batch(execution_id)',\n",
       "  'list_batch_execution': 'list_batch_execution()',\n",
       "  'swagger': 'swagger(json=True)'},\n",
       " 'published_by': 'admin',\n",
       " 'runtime': 'Realtime',\n",
       " 'snapshot_id': 'e787c12e-79e6-4d4a-85d8-59bd9f9a0015',\n",
       " 'swagger': 'http://localhost:12800/api/LinModService/1.0/swagger.json',\n",
       " 'version': '1.0'}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "service.capabilities()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since you are in the same session as the one you in which you deployed, you can consume it using the _service api_ object returned from `.deploy()`. You can verify that the results are as expected and that they match the results obtained when the model was run locally earlier. \n",
    "\n",
    "To consume the realtime service, call `.consume` on the realtime service object directly unless an alias was defined. In the case of an alias, call that alias instead. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   rating_Pred\n",
      "0    51.110295\n"
     ]
    }
   ],
   "source": [
    "# -- Consume the service. Pluck out the named output, outputData. --\n",
    "\n",
    "print(service.consume(df.head(1)).outputs['outputData'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you can grab the Swagger-based JSON file, which defines the service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "http://localhost:12800/api/LinModService/1.0/swagger.json\n"
     ]
    }
   ],
   "source": [
    "# -- Retrieve the URL of the swagger file for this service.\n",
    "cap = service.capabilities()\n",
    "swagger_URL = cap['swagger']\n",
    "print(swagger_URL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Learn more about how to [list, get, explore and consume web services in this notebook](https://github.com/Microsoft/ML-Server-Python-Samples/blob/master/operationalize/Explore_Consume_Python_Web_Services.ipynb).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Delete services\n",
    "\n",
    "You can call `delete_service` on the `DeployClient` object to delete a specific service on the running Machine Learning Server."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Uncomment this line if you want to delete the service now.\n",
    "#client.delete_service('LinModService', version='1.0')"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
